package edu.hawaii.senin.iclouds.basics;

import java.io.BufferedWriter;
import java.io.IOException;

import org.jdom.Element;

/**
 * Defines Molecule used in reactions.
 * 
 * @author Pavel Senin
 * 
 * 
 */
public class Molecule implements Specie, Comparable<Molecule> {

  /**
   * Molecule name.
   */
  private String name;

  /**
   * Molecule abundance.
   */
  private Double abundance;

  /**
   * Moleculeic fraction of abundance.
   */
  private Double fraction;

  /**
   * Molecule velocity.
   */
  private Double velocity;

  /**
   * Moleculeic mass unit.
   */
  private Double amu;

  /**
   * Holds probability of accretion.
   */
  private Double accretionProbability;

  /**
   * Holds accumulator value.
   */
  private Long accumulator;

  /**
   * Reports molecule activeness in the surface reactions.
   */
  private boolean isActive;

  /**
   * Reports which specie is the main to calculate fraction.
   */
  private String fractionOf;

  /**
   * Holds the site id where the specie resides.
   */

  /**
   * Instantiates new empty Molecule.
   * 
   * @param productName the name of the new molecule.
   * @param accValue the accumulator value to set up.
   * 
   */
  public Molecule(String productName, Long accValue) {
    this.name = productName;
    this.amu = 0.0;
    this.abundance = 0.0;
    this.fraction = 0.0;
    this.fractionOf = "";
    this.velocity = 0.0;
    this.accretionProbability = 0.0;
    this.accumulator = accValue;
    this.isActive = false;
  }

  /**
   * Instantiates new Molecule.
   * 
   * @param el the JDOM element to parse from.
   */
  public Molecule(Element el) {

    this.name = el.getChild("name").getValue();
    this.amu = Double.valueOf(el.getChild("amu").getValue());

    if (null == el.getChild("abundance")) {
      this.abundance = null;
    }
    else {
      this.abundance = Double.valueOf(el.getChild("abundance").getValue());
    }

    this.isActive = false;
    String type = el.getChild("type").getValue();
    if ("active".equalsIgnoreCase(type)) {
      this.isActive = true;
    }

    if (null == el.getChild("fraction")) {
      this.fraction = null;
    }
    else {
      this.fraction = Double.valueOf(el.getChild("fraction").getValue());
    }

    if (null == el.getChild("fraction_of")) {
      this.fractionOf = null;
    }
    else {
      this.fractionOf = el.getChild("fraction_of").getValue();
    }

    this.velocity = 0.0;
    this.accretionProbability = 0.0;
    this.accumulator = 0L;

    // if (((null != this.abundance) && (!this.abundance.equals(0D)))
    // && ((null != this.fraction) && (!this.fraction.equals(0D)))) {
    // this.abundance = this.abundance * this.fraction;
    // }
  }

  /**
   * Instantiates new Molecule with predefined properties.
   * 
   * @param abundance new abundance value.
   * @param accretionProbability new accretionProbability value.
   * @param amu new amu value.
   * @param fraction new fraction value.
   * @param frOf fraction calculation main specie.
   * @param name new name value.
   * @param velocity new velocity value.
   * @param accumulator accumulator value.
   * @param type defines this Molecule type /passive/active.
   */
  public Molecule(String name, Double amu, Double abundance, Double fraction, String frOf,
      Double accretionProbability, Double velocity, String type, Long accumulator) {

    this.name = name;
    this.amu = amu;
    this.abundance = abundance;
    this.fraction = fraction;
    this.fractionOf = frOf;
    this.velocity = velocity;
    this.accumulator = accumulator;
    this.accretionProbability = accretionProbability;
    if (type.equalsIgnoreCase("active")) {
      this.isActive = true;
    }
  }

  /** {@inheritDoc} */
  public void setAmu(Double amu) {
    this.amu = amu;
  }

  /** {@inheritDoc} */
  public Double getAmu() {
    return this.amu;
  }

  /** {@inheritDoc} */
  public void setAbundance(Double abundance) {
    this.abundance = abundance;
  }

  /** {@inheritDoc} */
  public Double getAbundance() {
    return this.abundance;
  }

  /** {@inheritDoc} */
  public void setVelocity(Double velocity) {
    this.velocity = velocity;
  }

  /** {@inheritDoc} */
  public Double getVelocity() {
    return this.velocity;
  }

  /** {@inheritDoc} */
  public void setName(String name) {
    this.name = name;
  }

  /** {@inheritDoc} */
  public String getName() {
    return this.name;
  }

  /**
   * Lame name comparator.
   * 
   * @param molecule1 molecule to compare with.
   * @return comparison result.
   */
  public int compareTo(Molecule molecule1) {
    return this.name.compareTo(molecule1.getName());
  }

  /**
   * Lame equal function.
   * 
   * @param o - object to compare with.
   * @return comparison result.
   */
  @Override
  public boolean equals(Object o) {
    if (o instanceof Molecule) {
      String str1 = this.getName();
      String str2 = ((Molecule) o).getName();
      return str1.equals(str2);
    }
    return false;

  }

  /**
   * Lame hashcode function.
   * 
   * @return lame hashcode.
   */
  @Override
  public int hashCode() {
    return 31 * this.name.hashCode() + this.name.hashCode();
  }

  /** {@inheritDoc} */
  public void setFraction(Double fraction) {
    this.fraction = fraction;
  }

  /** {@inheritDoc} */
  public Double getFraction() {
    return this.fraction;
  }

  /** {@inheritDoc} */
  public Double getRate(Double grainSize) {
    return this.abundance * this.velocity * Math.PI * grainSize;
  }

  /** {@inheritDoc} */
  public void setAccretionProbability(Double p) {
    this.accretionProbability = p;
  }

  /** {@inheritDoc} */
  public Double getAccretionProbability() {
    return this.accretionProbability;
  }

  /** {@inheritDoc} */
  public Long getAccumulatorValue() {
    return this.accumulator;
  }

  /** {@inheritDoc} */
  public void incrementAccumulatorValue(Long inc) {
    this.accumulator += inc;
  }

  /** {@inheritDoc} */
  public void setAccumulatorValue(Long value) {
    this.accumulator = value;
  }

  /** {@inheritDoc} */
  @Override
  public Molecule clone() {
    // public Molecule(String name, Double amu, Double abundance, Double fraction, String frOf,
    // Double accretionProbability, Double velocity, String type, Long accumulator) {
    String type = "passive";
    if (this.isActive) {
      type = "active";
    }
    return new Molecule(this.name, this.amu, this.abundance, this.fraction, this.fractionOf,
        this.accretionProbability, this.velocity, type, this.accumulator);
  }

  /** {@inheritDoc} */
  public void decrementAccumulatorValue(long dec) {
    this.accumulator -= dec;
  }

  /** {@inheritDoc} */
  public String toText() {
    StringBuffer sb = new StringBuffer(1000);

    sb.append(this.name + ": ");

    sb.append("amu " + this.amu);

    sb.append(", abundance " + this.abundance);

    if (null != this.fraction) {
      sb.append(", fr " + this.fraction);
    }

    if (this.isActive) {
      sb.append(", type: active");
    }
    else {
      sb.append(", type: passive");
    }

    sb.append(", vel: " + this.velocity);

    sb.append(", accum: " + this.accumulator + "\n");

    return sb.toString();
  }

  /** {@inheritDoc} */
  public boolean isActive() {
    return this.isActive;
  }

  /** {@inheritDoc} */
  public String getFractionOf() {
    if ((null != this.fractionOf) && (this.fractionOf.length() > 0)) {
      return this.fractionOf;
    }
    else {
      return null;
    }
  }

  /** {@inheritDoc} */
  public void setFractionOf(String specie) {
    this.fractionOf = specie;
  }

  /**
   * Dumps current instance into provided XML stream writer.
   * 
   * @param out sterm to write in.
   * @throws IOException when error encountered.
   */

  public void dump2XML(BufferedWriter out) throws IOException {
    // <molecule>
    // <name>H</name>
    // <amu>1.008</amu>
    // <abundance>1.0</abundance>
    // <type>active</type>
    // </molecule>
    StringBuffer sb = new StringBuffer(200);
    sb.append("<molecule>\n");
    sb.append("<name>" + this.name + "</name>\n");
    sb.append("<amu>" + this.amu + "</amu>\n");
    sb.append("<abundance>" + this.abundance + "</abundance>\n");

    if (null == this.fraction) {
      assert true;
    }
    else {
      sb.append("<fraction>" + this.fraction + "</fraction>\n");
      sb.append("<fraction_of>" + this.fractionOf + "</fraction_of>\n");
    }

    if (this.isActive) {
      sb.append("<type>active</type>\n");
    }
    else {
      sb.append("<type>passive</type>\n");
    }
    sb.append("</molecule>\n");
    out.write(sb.toString());
  }

  /** {@inheritDoc} */
  public Object[] toArray() {
    // private static final String[] ColumnNames = { "Specie", "Name", "AMU", "Abundance",
    // "Fraction", "Accretion P", "Type", "Velocity", "Accumulator" };
    Object[] res = new Object[9];
    res[0] = "atom";
    res[1] = this.name;
    res[2] = this.amu;
    res[3] = this.abundance;
    res[4] = this.fraction;
    res[5] = this.accretionProbability;
    if (this.isActive) {
      res[6] = "active";
    }
    else {
      res[6] = "passive";
    }
    res[7] = this.velocity;
    res[8] = this.accumulator;

    return res;

  }

}